/*
 * This file is part of ddesktop: remote desktop system for iPad 
 *
 * Copyright (C) 2010 Kazuhiro Satoh
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. if not, see <http://www.gnu.org/licenses/>.
 */

#import <sys/socket.h>
#import <arpa/inet.h>
#import <memory.h>
#import "DDClientPort.h"


@implementation DDClientPort

@synthesize screenSize = _screenSize;
@synthesize downloadFrame = _downloadFrame;
@synthesize downloadFrameCt = _downloadFrameCt;
@synthesize state = _state;

// getter
- (ImageBuf*) imageBufPt
{
	return &_imageBuf[_readyBufNum];
}

- (id)init
{
    if ((self = [super init]))
    {
		_commandSocketFd = 0;
		_dataSocketFd = 0;
		_dataListenSocketFd = 0;
		_screenSize = CGSizeMake(640, 480);
		_downloadFrame = CGRectMake(0, 0, 640, 480);
		_downloadFrameCt = 0;
		_state = PORT_STATE_CLOSED;
		
		memset(_imageBuf, 0, sizeof(_imageBuf));
		_readyBufNum = 0;
		
		{ // prepare image buf
			int size = 1024 * 768 * 4;  // big enough
			for (int i = 0; i < NUM_BUFFER; i++) {
				_imageBuf[i].buf = malloc(size);
				if (_imageBuf[i].buf == NULL) {
					@throw [NSException exceptionWithName:@"Error" reason:@"Failed to prepare image buffer" userInfo:nil];
				}
			}
		}
	}
	return self;
}

- (void)connectToHost:(NSString*)hostname port:(int)port
{	
	{ // open command socket
		struct sockaddr_in addr;
		
		// Create a socket
		_commandSocketFd = socket( AF_INET, SOCK_STREAM, 0 );
		if(_commandSocketFd < 0){
			@throw [NSException exceptionWithName:@"Error" reason:@"Failed to create command socket" userInfo:nil];
		}
		
		addr.sin_family = AF_INET;
		addr.sin_addr.s_addr = inet_addr([hostname UTF8String]);
		addr.sin_port = htons( port );
		
		int conn = connect(_commandSocketFd, (struct sockaddr*)&addr, sizeof(addr)); 
		
		if (conn) {
			@throw [NSException exceptionWithName:@"Error" reason:[@"Connection failed to host " stringByAppendingString:hostname] userInfo:nil];
		}
	}
	
	{ // prepare data socket
		struct sockaddr_in addr;
		
		_dataListenSocketFd = socket(AF_INET, SOCK_STREAM, 0);
		if(_dataListenSocketFd < 0){
			@throw [NSException exceptionWithName:@"Error" reason:@"Failed to create data socket" userInfo:nil];
		}
		
		addr.sin_family = AF_INET;
		addr.sin_addr.s_addr = INADDR_ANY;
		addr.sin_port = htons(0);
		
		if (bind(_dataListenSocketFd, (struct sockaddr *)&addr, sizeof(addr)) < 0){
			@throw [NSException exceptionWithName:@"Error" reason:@"Failed to bind data socket" userInfo:nil];
		}
		if (listen(_dataListenSocketFd, 0) < 0) {
			@throw [NSException exceptionWithName:@"Error" reason:@"Failed to listen data socket" userInfo:nil];
		}

		{
			int ret;
			unsigned int sock_buf_size = 600000;
			ret = setsockopt(_dataListenSocketFd, SOL_SOCKET, SO_RCVBUF, (char *)&sock_buf_size, sizeof(sock_buf_size) );
			if(ret != 0){
				@throw [NSException exceptionWithName:@"Error" reason:@"Failed to set socket buf size" userInfo:nil];
			}
		}
		
		socklen_t addrlen = sizeof(addr);
		if(getsockname(_dataListenSocketFd, (struct sockaddr *)&addr, &addrlen) == -1){
			@throw [NSException exceptionWithName:@"Error" reason:@"Failed to get socket port" userInfo:nil];
		}
		_dataPort = ntohs(addr.sin_port);
	}
	
	{ // create download thread
		[NSThread detachNewThreadSelector:@selector(downloadThread:) toTarget:self withObject:self];
	}
	
	{ // send download port number
		int result;
		char buf[6] = {DD_START_CODE, DD_SEND_DATA_PORT_NUMBER};
		*((uint32_t*)&(buf[2])) = htonl(_dataPort);
		result = send(_commandSocketFd, buf, sizeof(buf), MSG_WAITALL);
		if(result <= 0){
			@throw [NSException exceptionWithName:@"Error" reason:@"Failed to send port number" userInfo:nil];
		}
	}
	
	{ // recv screen size
		
		char buf[] = {DD_START_CODE, DD_REQUEST_SCREEN_SIZE};
		int result;
		result = send(_commandSocketFd, buf, sizeof(buf), MSG_WAITALL);
		if(result <= 0){
			@throw [NSException exceptionWithName:@"Error" reason:@"Failed to recv screen size" userInfo:nil];
		}
		uint32_t recvBuf[2];
		result = recv(_commandSocketFd, (char*)recvBuf, sizeof(recvBuf), MSG_WAITALL);
		if (result <= 0) {
			@throw [NSException exceptionWithName:@"Error" reason:@"Failed to recv screen size" userInfo:nil];
		}
		_screenSize = CGSizeMake(ntohl(recvBuf[0]), ntohl(recvBuf[1]));
	}
	
	_state = PORT_STATE_OPENED;
}


-(BOOL)changeDownloadFrame:(CGRect)frame zoomScale:(float)zoomScale
{
	int result;
	char command[2+4+4+4+4+4] = {DD_START_CODE, DD_CHANGE_DOWNLOAD_FRAME_RECT};
	*((uint32_t*)&(command[2])) = htonl((uint32_t)frame.origin.x);
	*((uint32_t*)&(command[6])) = htonl((uint32_t)frame.origin.y);
	*((uint32_t*)&(command[10])) = htonl((uint32_t)frame.size.width);
	*((uint32_t*)&(command[14])) = htonl((uint32_t)frame.size.height);
	*((uint32_t*)&(command[18])) = htonl((uint32_t)(zoomScale * 10000));
	result = send(_commandSocketFd, command, sizeof(command), MSG_WAITALL);

	return result > 0;
}


-(BOOL)startDownloading
{
	char buf[] = {DD_START_CODE, DD_START_DOWNSTREAM};
	int result;
	result = send(_commandSocketFd, buf, sizeof(buf), MSG_WAITALL);
	return result > 0;
}

-(BOOL)stopDownloading
{
	char buf[] = {DD_START_CODE, DD_STOP_DOWNSTREAM};
	int result;
	result = send(_commandSocketFd, buf, sizeof(buf), MSG_WAITALL);
	return result > 0;
}


- (BOOL)sendUIEvent:(enum DDUIEventType) eventType eventOwner:(enum DDUIEventOwner)eventOwner param1:(int)param1 param2:(int)param2
{
	char command[2+4+4+4+4] = {DD_START_CODE, DD_SEND_UI_EVENT};
	int result;
	*((uint32_t*)&command[2]) = htonl(eventType);
	*((uint32_t*)&command[2+4]) = htonl(eventOwner);
	*((uint32_t*)&command[2+4+4]) = htonl(param1);
	*((uint32_t*)&command[2+4+4+4]) = htonl(param2);
	result = send(_commandSocketFd, command, sizeof(command), MSG_WAITALL);
	return result > 0;
}


-(BOOL)sendKeyinputs:(NSString *)str
{
	uint32_t len = [str lengthOfBytesUsingEncoding:NSShiftJISStringEncoding];
	char command[2+4] = {DD_START_CODE, DD_SEND_KEYINPUTS};
	int result;
	*((uint32_t*)&command[2]) = htonl(len);
	result = send(_commandSocketFd, command, sizeof(command), MSG_WAITALL);
	if (result <= 0) {
		return false;
	}

	result = send(_commandSocketFd, [str cStringUsingEncoding:NSShiftJISStringEncoding], len, MSG_WAITALL);
	if (result <= 0) {
		return false;
	}
	return true;
}


- (void)changeImageQuality:(int)quality
{
	char command[2+4] = {DD_START_CODE, DD_CHANGE_IMAGE_QUALITY};
	int result;
	*((uint32_t*)&command[2]) = htonl(quality);
	result = send(_commandSocketFd, command, sizeof(command), MSG_WAITALL);
}


- (void)changeImageResolution:(int)resolution
{
	char command[2+4] = {DD_START_CODE, DD_CHANGE_IMAGE_RESOLUTION};
	int result;
	*((uint32_t*)&command[2]) = htonl(resolution);
	result = send(_commandSocketFd, command, sizeof(command), MSG_WAITALL);
}


// not used (for debug)
-(BOOL)downloadFrame:(CGRect)frame
{
	{
		int result;
		char command[2+4+4+4+4] = {DD_START_CODE, DD_REQUEST_IMAGE_DATA};
		*((uint32_t*)&(command[2])) = htonl((uint32_t)frame.origin.x);
		*((uint32_t*)&(command[6])) = htonl((uint32_t)frame.origin.y);
		*((uint32_t*)&(command[10])) = htonl((uint32_t)frame.size.width);
		*((uint32_t*)&(command[14])) = htonl((uint32_t)frame.size.height);
		result = send(_commandSocketFd, command, sizeof(command), MSG_WAITALL);
		if(result <= 0){
			return false;
		}
	}
	{
		int result;
		uint32_t length;
		result = recv(_commandSocketFd, &length, sizeof(length), MSG_WAITALL);
		if(result <= 0){
			return false;
		}
		length = ntohl(length);
		result = recv(_commandSocketFd, _imageBuf[0].buf + _imageBuf[0].headerSize, length, MSG_WAITALL);
		if(result <= 0){
			return false;
		}
		_imageBuf[0].dataSize = length;
		_readyBufNum = 0;
	}
		
	return true;
}


-(void)downloadThread:(id)param
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
	_dataSocketFd = accept(_dataListenSocketFd, NULL, NULL);
	if (_dataSocketFd != -1) {
		bool err = FALSE;
		uint8_t header[1+4*6];
		uint32_t* header_data = (uint32_t*)(&(header[1]));
		uint32_t recvSize;
		uint8_t* recvPt;
		uint32_t remain;
		int writeBufNum;
	
		const int MAX_PACKET_SIZE = 60000;
		while(!err){
			header[0] = 'x';
			while (header[0] != DD_START_CODE && !err) {
				err = recv(_dataSocketFd, header, sizeof(header), MSG_WAITALL) <= 0;
			}
			writeBufNum = (_readyBufNum + 1) % NUM_BUFFER;
			_imageBuf[writeBufNum].dataSize = ntohl(header_data[0]);
			_imageBuf[writeBufNum].x = ntohl(header_data[1]);
			_imageBuf[writeBufNum].y = ntohl(header_data[2]);
			_imageBuf[writeBufNum].width  = ntohl(header_data[3]);
			_imageBuf[writeBufNum].height = ntohl(header_data[4]);
			_imageBuf[writeBufNum].zoomScale = ntohl(header_data[5]) / 10000.0;
			remain = _imageBuf[writeBufNum].dataSize;
			recvPt = _imageBuf[writeBufNum].buf + _imageBuf[writeBufNum].headerSize;
			while (remain > 0) {
				recvSize = recv(_dataSocketFd, recvPt, MIN(remain, MAX_PACKET_SIZE), 0);
				if(recvSize <= 0){
					err = true;
					break;
				}
				remain -= recvSize;
				recvPt += recvSize;
			}
			_readyBufNum = writeBufNum;
			_downloadFrameCt++;
		}
		close(_dataSocketFd);
	}
	
	_state = PORT_STATE_ERROR;
	
    [pool release];
	
}


-(BOOL)close
{
	if(_dataSocketFd > 0){
		close(_dataSocketFd);
		_dataSocketFd = 0;
	}
	if(_dataListenSocketFd > 0){
		close(_dataListenSocketFd);
		_dataListenSocketFd = 0;
	}
	if(_commandSocketFd > 0){
		close(_commandSocketFd);
		_commandSocketFd = 0;
	}
	
	_state = PORT_STATE_CLOSED;

	return TRUE;
}


- (void)dealloc
{
	[self close];
	
	for(int i = 0; i < NUM_BUFFER; i++){
		if(_imageBuf[i].buf != NULL){
			free(_imageBuf[i].buf);
			_imageBuf[i].buf = NULL;
		}
	}
	
	[super dealloc];
}


@end
